/**
 * @brief There driver functions for ns16550
 * are implemented in this file.
 *
 * All function has no assumption for the
 * base address of the ns16550 instance on
 * any detail SoC or PCB board.
 */

#define __REG32(x) (*((volatile unsigned char *)(x)))

/* TRANSMIT AND RECEIVE HOLDING REGISTER */
#define UART_RBR()    __REG32(base + 0x00)
#define UART_THR()    __REG32(base + 0x00)

/* INTERRUPT ENABLE REGISTER */
#define UART_IER()    __REG32(base + 0x01)
/* INTERRUPT STATUS REGISTER */
#define UART_IIR()    __REG32(base + 0x02)
/* FIFO CONTROL REGISTER */
#define UART_FCR()    __REG32(base + 0x02)

/* LINE CONTROL REGISTER */
#define UART_LCR()    __REG32(base + 0x03)
/* Modem control register*/
#define UART_MCR()    __REG32(base + 0x04)
/* Line status register*/
#define UART_LSR()    __REG32(base + 0x05)
/* Modem status register*/
#define UART_MSR()    __REG32(base + 0x06)

// baud divisor rate register lsb and msb
#define UART_DLL()    __REG32(base + 0x00)
#define UART_DLM()    __REG32(base + 0x04)

#include "typedef.h"

/**
The steps for the transmitting character data through the UART 16550 IP Core is shown below. This step assumes that
the IP core is not performing transmit operation or at least the XMIT FIFO is empty.

1. If MODEM control signals are used, set MCR.rts_ctrl=1’b1. This asserts rts_n_o signal, telling the peripheral device
   that the UART would like to transmit data. In response to this, peripheral device asserts cts_n_i if it is ready to
   receive data.

2. Write data to THR. You can write up to 16 character data.

3. Set IER.thre_int_en=1’b1 to enable Transmit Holding Register Empty interrupt

4. Wait for Transmit Holding Register Empty interrupt to assert.
    4.1 Using interrupt: Wait for interrupt assertion and check that IIR[3:0]= 4’b0010.
    4.2 Polling: Read LSR until the thr_empty bit asserts. Note that Step 3 can be skipped for polling mode.

5. If you need to send more characters, write data to THR. You can write up to 16 character data.

6. Repeat Steps 4-5 until all characters are sent.

7. Optional: If MODEM control signals are used, poll LSR until xmitr_empty bit is high. This ensure that the UART
   transmission of last character is completed. Set MCR.rts_ctrl=1’b0 to negate the rts_n_o signal.
*/

/* TRANSMIT AND RECEIVE HOLDING REGISTER */

/* INTERRUPT ENABLE REGISTER */

#define UART_IER_RX_ENABLE (1 << 0)
#define UART_IER_TX_ENABLE (1 << 1)

/* FIFO CONTROL REGISTER */
#define UART_FCR_FIFO_ENABLE (1 << 0)
#define UART_FCR_FIFO_CLEAR (3 << 1)

/* INTERRUPT STATUS REGISTER */
#define UART_ISR 2

/* LINE CONTROL REGISTER */
#define UART_LCR_EIGHT_BITS (3 << 0)
// special mode to set baud rate
#define UART_LCR_BAUD_LATCH (1 << 7)

/* LINE STATUS REGISTER */
#define UART_LSR_RX_READY (1 << 0) // input is waiting to be read from RHR

#define UART_LSR_TX_IDLE (1 << 5) // THR can accept another character to send

#define UART_REFERENCE_CLOCK  1843200
#define UART_DEFAULT_BAUDRATE 115200

int uart_init(address base)
{
    word div = UART_REFERENCE_CLOCK / (UART_DEFAULT_BAUDRATE * 16);

    UART_IER() = 0x0;
    UART_LCR() = UART_LCR_BAUD_LATCH;

    UART_DLL() = div & 0xff;
    UART_DLM() = (div >> 8) & 0xff;

    UART_LCR() = UART_LCR_EIGHT_BITS;
    UART_FCR() = UART_FCR_FIFO_ENABLE | UART_FCR_FIFO_CLEAR;
    return 0;
}

int uart_putc(address base, char c)
{
    static boolean initiliized = 0;
    if (!initiliized)
    {
        uart_init(base);
        initiliized = 1;
    }

    word a = UART_LSR();
    while (0 == (a & UART_LSR_TX_IDLE))
    {
    }
    UART_THR() = c;
    return 0;
}

char uart_getc(address base)
{
    word a = UART_LSR();
    while (0 == (a & UART_LSR_RX_READY))
    {
    }
    char c = UART_RBR();
    return c;
}